#include <stdio.h>
extern "C" {
  #include "drecodec.h"

};
#include "ptypes.h"
#include "rabinfp.h"
#include "packeteer.h"


void * InitReEngine(int context, const char *db_name) {
  Packeteer *cache = new Packeteer(context);
  if (cache->Initialize(context, db_name))
    return (void *)cache;
  else
    return NULL;
}


void ShutdownReEngine(void *engine) {
  Packeteer *cache = (Packeteer *)engine;
  delete cache;
}

/*
  in case of ReENotCompressedStored, Encode only fills in vp and lengths, and does NOT
  copy data into the outbuffer
  in case of ReCompressed* returns, data is copied
*/
int ReEncode(void *engine, unsigned char *inbuffer, int insize,
                       unsigned char *outbuffer, int out_length, ReStatus *vp,
                       unsigned short *lengths, int pair_count)
{
  Packeteer *cache = (Packeteer *)engine;

  if (!vp || !lengths || !inbuffer || !outbuffer)
    return ReEGeneralFailure;

  if (pair_count < (insize + MAX_CHUNK_SIZE -1) / MAX_CHUNK_SIZE)
    return ReEInsufficientBuffer;

  if (insize <= MAX_CHUNK_SIZE) {
    /* optimized case */
    int l;
    ReEncodingStatus ret = cache->Encode(inbuffer, insize, outbuffer, &l);
    if (ret < 0)
      return ret;
    vp[0].encoding = ret;
    lengths[0] = (unsigned short) l;
    return (ret == ReENotCompressedStored ? 0 : l);
  }
  else {
    int processed_input_bytes = 0, received_output_bytes = 0;
    bool copy_happened = false;
    int chunk_count = 0;

    while(processed_input_bytes < insize) {
      // next chunk
      int size = (insize - processed_input_bytes) <= MAX_CHUNK_SIZE ?
        (insize - processed_input_bytes) : MAX_CHUNK_SIZE;
      int outs;
      ReEncodingStatus ret = cache->Encode(inbuffer+processed_input_bytes,
		size, outbuffer+received_output_bytes, &outs);
      if (ret < 0) {
        // if we have completed any previous operations, and have now failed,
        // it is serious, we will have to undo the operations that have succeeded,
        // or inform the other side of the dictionary change.
        // just die for now TODO
        assert(chunk_count == 0);
        return ret;
      }
      if (ret == ReECompressedStored || ret == ReECompressedNotStored) {
        /* data is in outbuf */
        if (!copy_happened) {
          assert(received_output_bytes == 0);
          /* if we haven't copied anything to output buffer yet, shift what we have now */
          if (processed_input_bytes > 0) {
            unsigned char savebuf[MAX_CHUNK_SIZE];
            /* first, save bytes that are in outbuf */
            memcpy(savebuf, outbuffer, outs);
            memcpy(outbuffer, inbuffer, processed_input_bytes);
            /* put saved bytes in place */
            memcpy(outbuffer+processed_input_bytes, savebuf, outs);
            /* outbuffer has data from input plus what we just compressed, outs added outside of if */
            received_output_bytes += processed_input_bytes;
          }
          else {
            /* the chunk we just received is already in outbuffer, no need to copy */
          }
        }
        copy_happened = true;
        /* the chunk we just received is already in outbuffer+received_output_bytes, no need to copy */
        received_output_bytes += outs;
        processed_input_bytes += size;
        // record the results
        vp[chunk_count].encoding = ret;
        lengths[chunk_count] = (unsigned short)outs;
        chunk_count++;
      }
      else if (ret == ReENotCompressedStored) {
        /* data is in inbuf */
        if (copy_happened) {
          /* shift to output buffer */
          memcpy(outbuffer+received_output_bytes, inbuffer+processed_input_bytes, outs);
          received_output_bytes += outs;
        }
        else {
          assert(size == outs);
          /* otherwise no need to do anything */
        }
        processed_input_bytes += size;
        // record results
        vp[chunk_count].encoding = ret;
        lengths[chunk_count] = (unsigned short)outs;
        chunk_count++;
      }
      else {
        /* illegal return code */
        assert(0);
      }
    }
    return copy_happened ? received_output_bytes : 0;
  }
}

int ReDecode(void *engine, unsigned char *inbuffer, int insize, 
	     unsigned char *outbuffer, int outsize, ReStatus *vp,
             unsigned short *lengths, int pair_count)
{
  if (!vp || !lengths || !inbuffer || !outbuffer || pair_count < 0)
    return ReDGeneralFailure;

  Packeteer *cache = (Packeteer *)engine;

  if (pair_count == 1) {
    // optimized special case
    int l;
    ReEncodingStatus stat = vp[0].encoding;
    if (stat < 0)
      return ReDGeneralFailure;
    ReDecodingStatus ret = cache->Decode(inbuffer, insize, outbuffer, &l, stat);
    if (ret < 0)
      return ret;
    vp[0].decoding = ret;
    lengths[0] = (unsigned short)l;
    return (ret == ReDPacketStored ? 0 : l);
  }
  else {
    int processed_input_bytes = 0, received_output_bytes = 0;
    bool copy_happened = false;

    for(int chunk_count = 0; chunk_count < pair_count; chunk_count++) {
      int outs;
      int size = (int)lengths[chunk_count];
      ReEncodingStatus stat = vp[chunk_count].encoding;
      if (!stat || !size) // 0 in status or size means we hace reached non-touched array part
        break;
      ReDecodingStatus ret;
      if (stat > 0)
        ret = cache->Decode(inbuffer+processed_input_bytes, size, outbuffer+received_output_bytes, &outs, stat);
      else
        ret = ReDGeneralFailure;
      if (ret < 0) {
        // if we have completed any previous operations, and have now failed,
        // it is serious, we will have to undo the operations that have succeeded,
        // or inform the other side of the dictionary change.
        // just die for now TODO
        assert(chunk_count == 0);
        return ret;
      }
      if (ret == ReDPacketDecoded || ret == ReDPacketDecodedStored) {
        /* data is in outbuf */
        if (!copy_happened) {
          assert(received_output_bytes == 0);
          /* if we haven't copied anything to output buffer yet, shift what we have now */
          if (processed_input_bytes > 0) {
            unsigned char savebuf[MAX_CHUNK_SIZE];
            /* first, save bytes that are in outbuf */
            memcpy(savebuf, outbuffer, outs);
            memcpy(outbuffer, inbuffer, processed_input_bytes);
            /* put saved bytes in place */
            memcpy(outbuffer+processed_input_bytes, savebuf, outs);
            /* outbuffer has data from input plus what we just compressed, outs added outside of if */
            received_output_bytes += processed_input_bytes;
          }
          else {
            /* the chunk we just received is already in outbuffer, no need to copy */
          }
        }
        copy_happened = true;
        /* the chunk we just received is already in outbuffer+received_output_bytes, no need to copy */
        received_output_bytes += outs;
        processed_input_bytes += size;
        // record the results
        vp[chunk_count].decoding = ret;
        lengths[chunk_count] = (unsigned short)outs;
      }
      else if (ret == ReDPacketStored) {
        /* data is in inbuf */
        if (copy_happened) {
          /* shift to output buffer */
          memcpy(outbuffer+received_output_bytes, inbuffer+processed_input_bytes, outs);
          received_output_bytes += outs;
        }
        else {
          assert(size == outs);
          /* otherwise no need to do anything */
        }
        processed_input_bytes += size;
        // record results
        vp[chunk_count].decoding = ret;
        lengths[chunk_count] = (unsigned short)outs;
      }
      else {
        /* illegal return code */
        assert(0);
      }
    }
    return copy_happened ? received_output_bytes : 0;
  }
}

bool ReGetStats(void *engine, struct ReEngineStats *pstats)
{
  Packeteer *cache = (Packeteer *)engine;

  pstats->disk_cache_purges = cache->m_disk.GetStatsPurges();
  pstats->disk_cache_gets = cache->m_disk.GetStatsGets();
  pstats->disk_cache_saves = cache->m_disk.GetStatsSaves();

  pstats->index_puts = cache->m_disk.m_index.GetStatsPuts();
  pstats->index_deletes = cache->m_disk.m_index.GetStatsDeletes();
  pstats->index_found = cache->m_disk.m_index.GetStatsFound();
  pstats->index_notfound = cache->m_disk.m_index.GetStatsNotFound();

  return true;
}
